package no.sachmuld.methodcombination.api.reflection.component;

import no.sachmuld.methodcombination.api.BehaviourMethod;
import no.sachmuld.methodcombination.impl.AbstractMethodInvocation;
import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.Direction;
import no.sachmuld.methodcombination.api.MethodInvocation;

import java.lang.reflect.Method;

/**
 *
 */
public class AroundMethodCombination<B, R> extends DelegatingMethodCombination<B, R> {

    private MethodCombination<B, R> inner;
    final private String primaryMethodName;
    final private Direction direction;

    public AroundMethodCombination(String primaryMethodName, Direction direction) {
        this.primaryMethodName = primaryMethodName;
        this.direction = direction;
    }

    public AroundMethodCombination<B, R> inner(MethodCombination<B, R> inner) {
        this.inner = inner;
        return this;
    }

    @Override
    public AroundMethodCombination<B, R> build(final Class behaviourClass, final Method targetMethod) {

        final BehaviourMethod aroundMethod = new BehaviourMethod.Builder(behaviourClass, targetMethod, false).prefix("around").name(primaryMethodName).extraParameterTypes(MethodInvocation.class).build();

        validatedAgainst(aroundMethod);

        executedBy(new AbstractMethodCombination<B, R>() {
            @Override
            public R execute(final Object targetObject, final BehaviourChain<? extends B> behaviourChain, final Object... parameters) {
                if (aroundMethod.exists()) {
                    return new AbstractMethodInvocation<Object, R>(behaviourChain.iterable(direction), targetObject, targetMethod, parameters) {
                        @Override
                        protected R executeBehaviour(Object behaviour) {

                            if (behaviourClass.isAssignableFrom(behaviour.getClass())) {
                                return (R) aroundMethod.invoke(behaviour, parameters, this);
                            } else {
                                return proceed();
                            }
                        }

                        @Override
                        protected R atEnd() {
                            return inner.execute(targetObject, behaviourChain, parameters);
                        }
                    }.execute();

                } else {
                    return inner.execute(targetObject, behaviourChain, parameters);
                }
            }
        });

        return this;

    }

}
